package com.newegg.research.tfidfAnalyzer;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

/**
 * 根据标准TFIDF概念以及余弦定理进行文本分类的工具包
 * @author Jin.H.Gao@NeweggLab
 * @since 2008.04.08
 * @version 0.1
 */
public class Indexer implements Serializable
{
	/**
	 * 
	 */
	private static final long serialVersionUID = 3610112063728347786L;

	/** 日志记录 */
	public static Logger logger = Logger.getLogger(Indexer.class.getName());

	private Map<String, Category> all_categories = new HashMap<String, Category>();

	private Map<String, Token> all_tokens = new HashMap<String, Token>();

	private int words_count = 0;

	/**
	 * 根据训练数据建造索引
	 * 
	 * @param word -
	 *            已切分过的词
	 * @param category -
	 *            当前词的类别
	 */
	public void index(String word, String category)
	{
		Token t = getToken(word, true);
		Category c = getCategory(category, true);
		t.increaseCount(c);
		c.addToken(t);
		words_count++;
	}
	
	public String debug()
	{
		return "当前索引词" + words_count + "个; 标准类别共" + all_categories.size() + "个";
	}
	
	public void removeIndex(Token t)
	{
		List<Category> categorys = t.getAllRelatedCategory();
		for(Category c : categorys)
		{
			c.getAllTokenAsMap().remove(t);
		}
		all_tokens.remove(t);
		words_count--;
	}
	
	public void removeIndex(String word)
	{
		Token t = getToken(word, false);
		if(t == null)
			return;
		else
		{
			removeIndex(t);
		}
	}

	/**
	 * 获取当前所有Token数
	 * @return	当前所有Token的数目
	 */
	public int getAllTokenNum()
	{
		return words_count;
	}

	/**
	 * 根据Token内容获取Token
	 * @param word	- Token的内容
	 * @param createNewOne	- 当请求的Token不存在时是否重建一个新的。当为否时将返回null
	 * @return	请求的Token
	 */
	public Token getToken(String word, boolean createNewOne)
	{
		if (word == null)
			throw new IllegalArgumentException(
					"The requested category name cannot be null");

		word = word.toLowerCase();

		if (all_tokens.containsKey(word))
		{
			return all_tokens.get(word);
		} else if (createNewOne)
		{
			Token token = new Token(word);
			all_tokens.put(word, token);
			return token;
		} else
			return null;
	}

	/**
	 * 根据Category内容获取Category
	 * @param categoryName	- Category的内容
	 * @param createNewOne	- 当请求的Category不存在时是否重建一个新的。当为否时将返回null
	 * @return	请求的Category
	 */
	public Category getCategory(String categoryName, boolean createNewOne)
	{
		if (categoryName == null)
			throw new IllegalArgumentException(
					"The requested category name cannot be null");

		if (all_categories.containsKey(categoryName))
		{
			return all_categories.get(categoryName);
		} else if (createNewOne)
		{
			Category instance = new Category(categoryName);
			all_categories.put(categoryName, instance);
			return instance;
		} else
			return null;
	}

	/**
	 * 计算当前词的IDF值
	 * @param t	 － 需要计算的Token
	 * @return	- 对应的IDF值
	 */
	public double calcIDF(Token t)
	{
		double d;
		if (t.getSumCount() != 0)
		{
			d = Math.log(words_count / t.getSumCount()) / Math.log(2);
		} else
		{
			d = 0;
		}

		return d;
	}

	private double calcIDF(String s)
	{
		Token t = getToken(s, false);
		return t == null ? 0 : calcIDF(t);
	}

	/**
	 * 获取当前所有类别
	 * @return 当前所有类别的集合
	 */
	public List<Category> getAllCategory()
	{
		List<Category> l = new ArrayList<Category>(all_categories.size());
		Collection<Category> c = all_categories.values();
		Iterator<Category> it = c.iterator();
		while (it.hasNext())
			l.add(it.next());
		return l;
	}

	/**
	 * 计算一个集合的词和一个指定类别的余弦值。需要注意的是返回值是输入的Tokens和Category的余弦值与1的绝对差，即返回的值越小，则输入的Tokens和Category的相似度越高。而加权系数是指假如我们输入两个词“中国”和“新蛋”，在排除TF、DF、CF的情况下，很明显后者的权重更高，这样就可以通过手动增加“新蛋”的权重
	 * @param t - 需要计算余弦值的词。
	 * @param c - 需要进行比对的类别
	 * @param weights - 分别对应每个词的加权系数。加权系数的数目必须与输入的token数目相同，否则加权系数将会不生效。如果不需要加权的话可以直接输入null
	 * @return - 输入的t和c的差别度。返回值越小则两者越相关
	 */
	public double acos(List<Token> t, Category c, List<Double> weights)
	{
		double factor = 0;
		int count = 0;
		boolean weightEnable = weights != null;
		if (weightEnable && t.size() != weights.size())
		{
			logger
					.warning("The number of the tokens and weights is not the same, now disable weights forcely");
			weightEnable = false;
		}
		for (Token t1 : t)
		{
			if (t1.getCountInCategory(c) > 0)
			{
				if (weightEnable)
					factor += t1.getCountInCategory(c)
							* Math.pow(calcIDF(t1), 2) * weights.get(count++);
				else
					factor += t1.getCountInCategory(c)
							* Math.pow(calcIDF(t1), 2);
			}
		}

		if (factor == 0)
		{
			factor = 0;
		}

		double result = Math
				.abs(1 - (factor / (getFactor1(c) * getFactor2(t))));
		return result;
	}

	@SuppressWarnings("unchecked")
	private double getFactor1(Category c)
	{
		List<Token> tokens = c.getAllToken();
		double factor1 = 0;
		for (Token t : tokens)
		{
			double idf = calcIDF(t);
			factor1 += Math.pow(t.getCountInCategory(c) * idf, 2);
		}
		return Math.sqrt(factor1);
	}

	private double getFactor2(List<Token> t)
	{
		double factor = 0;
		for (Token t1 : t)
		{
			factor += Math.pow(calcIDF(t1), 2);
		}
		return Math.sqrt(factor);
	}

	/**
	 * 获取所有的Category集合
	 * @return Map形式的Category的集合
	 */
	public Map<String, Category> getAllCategoriesAsMap()
	{
		return all_categories;
	}

	/**
	 * 获取所有的Token集合
	 * @return Map形式的Token的集合
	 */
	public Map<String, Token> getAllTokensAsMap()
	{
		return all_tokens;
	}

	/**
	 * 获取所有的Token集合
	 * @return List形式的Token的集合
	 */
	public List<Token> getAllTokens()
	{
		Collection<Token> c = all_tokens.values();
		List<Token> tokens = new ArrayList<Token>(all_tokens.size());
		Iterator<Token> it = c.iterator();
		while (it.hasNext())
		{
			tokens.add(it.next());
		}
		return tokens;
	}

}
